﻿using ssize_t = size_t;
#include <stddef.h>
#include <stdint.h>
extern "C" {
#include <vlc/vlc.h>
}
#include "VideoItem2.h"
#include <QOpenGLFramebufferObjectFormat>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>

static const char* vertexShaderSource =
"#version 450 core\n"
"layout(location = 0) in vec2 position;\n"
"layout(location = 1) in vec2 textureIn;\n"
"layout(location = 0) out vec2 texcoord;\n"
"void main()\n"
"{\n"
"    gl_Position = vec4(position, 0.0, 1.0);\n"
"	 texcoord = textureIn;\n"
"}\n";

static const char* fragmentShaderSource =
"#version 450 core\n"
"#ifdef GL_ES\n"
"precision highp float;\n"
"#endif\n"
"layout(binding = 1) uniform sampler2D texture;\n"
"layout(location = 0) in vec2 texcoord;\n"
"out vec4 fragColor;\n"
"void main()\n"
"{\n"
"    fragColor = texture2D(texture, texcoord);\n"
"}\n";
static const GLfloat g_vertex_buffer_data[] = {
	-1.0f, -1.0f,
	 1.0f, -1.0f,
	-1.0f,  1.0f,
	 1.0f,  1.0f
};
static const GLushort g_element_buffer_data[] = { 3, 2, 1, 0 };

static const GLfloat g_tex_vertex_buffer_data[] = {	
	0.0f, 0.0f,
	1.0f, 0.0f,
	0.0f, 1.0f,
	1.0f, 1.0f,
};

class VideoGlRender : public QOpenGLFunctions
{
public:
	QOpenGLShaderProgram program1;
	QOpenGLBuffer vertexBuffer, vertexIndexBuffer, texVertexBuffer;
	int m_width = 1920;
	int m_height = 1080;
	GLuint m_texture = 0;
	uint8_t* m_pPixelData = nullptr;
	VideoGlRender() {

	}
	~VideoGlRender() {

	}
	void resize(QSize const& size) {
		m_width = size.width();
		m_height = size.height();
	}
	void initialize() {
		initializeOpenGLFunctions();
		glClearColor(0.0, 0.0, 0.0, 1);

		vertexBuffer.create();
		vertexBuffer.bind();
		vertexBuffer.allocate(g_vertex_buffer_data, sizeof(g_vertex_buffer_data));

		vertexIndexBuffer.create();
		vertexIndexBuffer.bind();
		vertexIndexBuffer.allocate(g_element_buffer_data, sizeof(g_element_buffer_data));

		texVertexBuffer.create();
		texVertexBuffer.bind();
		texVertexBuffer.allocate(g_tex_vertex_buffer_data, sizeof(g_tex_vertex_buffer_data));

		program1.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
		program1.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
		program1.link();
		//使用opengl生成纹理
		glGenTextures(1, &m_texture);
		glBindTexture(GL_TEXTURE_2D, m_texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
	}
	void render() {
		if (!m_pPixelData) {
			return;
		}
		program1.bind();
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, m_texture);
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_pPixelData);
		program1.setUniformValue("texture", 0);
		vertexBuffer.bind();
		program1.enableAttributeArray(0);
		program1.setAttributeBuffer(0, GL_FLOAT, 0, 2);
		texVertexBuffer.bind();
		program1.enableAttributeArray(1);
		program1.setAttributeBuffer(1, GL_FLOAT, 0, 2);
		vertexIndexBuffer.bind();
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
		//glDrawElements(
		//	GL_TRIANGLE_STRIP,  /* mode */
		//	4,                  /* count */
		//	GL_UNSIGNED_SHORT,  /* type */
		//	(void*)0            /* element array buffer offset */
		//);
		program1.disableAttributeArray(0);
		program1.disableAttributeArray(1);
		program1.release();
	}
};

class VideoReder : public QQuickFramebufferObject::Renderer
{
public:
	VideoReder() {
		video.initialize();
	}

	void render() override {
		video.render();
		update();
	}

	void setCurrent(uint8_t* pPixelData) {
		video.m_pPixelData = pPixelData;
	}

	QOpenGLFramebufferObject* createFramebufferObject(const QSize& size) override {
		QOpenGLFramebufferObjectFormat format;
		format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
		format.setSamples(4);
		video.resize(QSize(1920, 1080));
		return new QOpenGLFramebufferObject(QSize(1920,1080), format);
	}

	VideoGlRender video;
};


VideoItem2::VideoItem2(QQuickItem* parent /*= nullptr*/)
	: QQuickFramebufferObject(parent) {
	setFlag(ItemHasContents, true);
	const char* args[] = {
	"--verbose=2"
	};
	m_vlc = libvlc_new(sizeof(args) / sizeof(*args), args);
}

VideoItem2::~VideoItem2() {
	cleanup();
}

bool VideoItem2::play() {
	stop();
	QString urlStr = m_source;
	if (!QUrl(m_source).isValid()) {
		urlStr = QUrl::fromLocalFile(m_source).toString();
	}
	m_media = libvlc_media_new_location(m_vlc, urlStr.toLocal8Bit().data());
	if (!m_media) {
		qWarning() << QString("unable to create media %1").arg(urlStr);
		return false;
	}
	m_mp = libvlc_media_player_new_from_media(m_media);
	if (!m_mp) {
		qWarning() << QString("unable to create media player");
		libvlc_media_release(m_media);
		return false;
	}
	libvlc_video_set_callbacks(m_mp, &VideoItem2::lock, &VideoItem2::unlock, &VideoItem2::display, this);
	libvlc_video_set_format(m_mp, "RGBA", 1920, 1080, 1920 * 4);

	//对libvlc绑定错误事件
	libvlc_event_manager_t* eventManager = libvlc_media_player_event_manager(m_mp);
	libvlc_event_attach(eventManager, libvlc_MediaPlayerEndReached, &VideoItem2::error, this);
	libvlc_event_attach(eventManager, libvlc_MediaPlayerEncounteredError, &VideoItem2::error, this);
	libvlc_event_attach(eventManager, libvlc_MediaPlayerPlaying, &VideoItem2::onPlaying, this);
	libvlc_event_attach(eventManager, libvlc_MediaPlayerStopped, &VideoItem2::onStopped, this);
	libvlc_media_add_option(m_media, "vcd-caching=200");
	libvlc_media_add_option(m_media, "sout-rtp-caching=200");
	libvlc_media_add_option(m_media, "network-caching=200");
	libvlc_media_add_option(m_media, "clock-jitter=0");
	libvlc_media_add_option(m_media, "clock-synchro=0");
	libvlc_media_add_option(m_media, "clock-master=0");
	libvlc_media_add_option(m_media, "clock-source=0");
	libvlc_media_add_option(m_media, "clock-description=monotonic");
	if (m_pPixelData == nullptr) {
		m_pPixelData = new unsigned char[1920 * 1080 * 4];
	}
	// Play the video
	libvlc_media_player_play(m_mp);
	return true;
}

void VideoItem2::stop() {
	if (m_mp) {
		libvlc_media_player_stop(m_mp);
		libvlc_media_player_release(m_mp);
		m_mp = nullptr;
	}
	if (m_media) {
		libvlc_media_release(m_media);
		m_media = nullptr;
	}
	m_hasError = false;
	emit errorChanged();
}

bool VideoItem2::playing() const {
	return m_playing;
}

bool VideoItem2::hasError() const {
	return hasError();
}

QString VideoItem2::source() const {
	return m_source;
}

void VideoItem2::setSource(QString const& source) {
	if (m_source != source) {
		m_source = source;
		emit sourceChanged();
		if (m_autoPlayAsSourceChanged && !m_source.isEmpty()) {
			play();
		}
	}
}

bool VideoItem2::autoPlayAsSourceChanged() const {
	return m_autoPlayAsSourceChanged;
}

void VideoItem2::setAutoPlayAsSourceChanged(bool autoPlayAsSourceChanged) {
	if (m_autoPlayAsSourceChanged != autoPlayAsSourceChanged) {
		m_autoPlayAsSourceChanged = autoPlayAsSourceChanged;
		emit setAutoPlayAsSourceChangedChanged();
		if (m_autoPlayAsSourceChanged && !m_source.isEmpty()) {
			play();
		}
	}
}

QQuickFramebufferObject::Renderer* VideoItem2::createRenderer() const {
	const_cast<VideoReder*&>(m_renderer) = new VideoReder;
	return m_renderer;
}

QSGNode* VideoItem2::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) {
	return QQuickFramebufferObject::updatePaintNode(oldNode, updatePaintNodeData);
}

void VideoItem2::cleanup() {
	m_renderer = nullptr;
	stop();
	if (m_vlc)
		libvlc_release(m_vlc);
	m_vlc = nullptr;
}

void* VideoItem2::lock(void* data, void** p_pixels) {
	VideoItem2* p = static_cast<VideoItem2*>(data);
	if (p) {
		*p_pixels = p->m_pPixelData;
		p->m_mutex.lock();
	}
	return nullptr;
}

void VideoItem2::unlock(void* data, void* id, void* const* p_pixels) {
	VideoItem2* p = static_cast<VideoItem2*>(data);
	if (p) {
		p->m_mutex.unlock();
	}
}

void VideoItem2::display(void* data, void* id) {
	VideoItem2* player = reinterpret_cast<VideoItem2*>(data);
	if (player) {
		if (player->m_renderer) {
			player->m_renderer->setCurrent(player->m_pPixelData);
		}
		QMetaObject::invokeMethod(player, "update");
	}
}

void VideoItem2::error(const struct libvlc_event_t* p_event, void* data) {
	VideoItem2* player = reinterpret_cast<VideoItem2*>(data);
	if (player) {
		player->m_hasError = true;
		emit player->errorChanged();
		QMetaObject::invokeMethod(player, "stop");
		emit player->errorOccurred();
	}
}

void VideoItem2::onPlaying(const struct libvlc_event_t* p_event, void* data) {
	VideoItem2* player = reinterpret_cast<VideoItem2*>(data);
	if (player) {
		player->m_playing = true;
		emit player->playingChanged();
	}
}

void VideoItem2::onStopped(const struct libvlc_event_t* p_event, void* data) {
	VideoItem2* player = reinterpret_cast<VideoItem2*>(data);
	if (player) {
		player->m_playing = false;
		emit player->playingChanged();
	}
}

//#include "VideoItem.moc"